package no.group.gowther.service;

import cn.hutool.core.io.FileUtil;
import no.group.gowther.enums.FileType;
import no.group.gowther.exception.*;
import no.group.gowther.service.dto.*;
import no.group.gowther.service.dto.param.FileDeleteDTO;
import no.group.gowther.service.dto.param.FileQueryListDTO;
import no.group.gowther.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author ayatsuji
 * @date 2022-04-12 23:04
 * @since
 */
@Service
public class FileManageServiceImpl implements FileManageService {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileManageServiceImpl.class);

    @Autowired
    private ImageService imageService;

    @Autowired
    private FileFilter fileFilter;

    @Override
    public ListDTO<FileDTO> listFile(FileQueryListDTO queryDTO) {
        if (queryDTO.getDirPath().isPresent()) {
            Path queryDirPath = queryDTO.getDirPath().get();
            FileType queryFileType = queryDTO.getFileType().orElse(FileType.ALL);

            try {
                Predicate<Path> predicate = fileFilter.getFilter().and(queryFileType::isMyType);
                long total = FileUtils.fileCount(queryDirPath, predicate);
                List<Path> filePaths = queryDTO.getPage().isPresent()
                        ? FileUtils.listFileBy(queryDirPath, predicate, queryDTO.getPage().get())
                        : FileUtils.listFileBy(queryDirPath, predicate);
                List<FileDTO> fileDTOS = newFileDTO(filePaths);
                return new ListDTO<>(total, fileDTOS);
            } catch (IOException e) {
                LOGGER.error("获取路径=[{}]文件列表失败", queryDirPath, e);
                throw BusinessException.createException(ErrorMessage.SYSTEM_ERROR.getErrorCode(), "获取文件列表失败");
            }
        }
        // 没有提供目录，就展示根目录
        List<FileDTO> fileDTOs = listRoot();
        return new ListDTO<>(fileDTOs.size(), fileDTOs);
    }

    private List<FileDTO> listRoot() {
        List<Path> roots = FileUtils.listRoot();
        return newFileDTO(roots);
    }

    private List<FileDTO> newFileDTO(List<Path> files) {
        List<FileDTO> fileDTOs = new ArrayList<>(files.size());
        for (Path file : files) {
            FileDTO fileDTO = new FileDTO();
            fileDTO.setFile(file);
            fileDTO.setType(FileType.resolveType(file));
            if (fileDTO.getType() == FileType.IMAGE) {
                fileDTO.setImageCode(imageService.generateImageCode(file));
            }
            fileDTO.setEmpty(FileUtil.isEmpty(fileDTO.getFile().toFile()));
            fileDTO.setContainsImage(isDirContainsImage(fileDTO));
            fileDTOs.add(fileDTO);
        }
        return fileDTOs;
    }

    private boolean isDirContainsImage(FileDTO fileDTO) {
        if (fileDTO.getType() == FileType.DIR) {
            try {
                return FileUtils.fileCount(fileDTO.getFile(), FileType.IMAGE::isMyType) > 0;
            } catch (IOException e) {
                LOGGER.error("判断目录中是否有图像失败，dir=[{}]", fileDTO.getFile(), e);
            }
        }
        return false;
    }

    @Override
    public void deleteFile(FileDeleteDTO deleteDTO) {
        final LineErrorMsgBuilder lineErrorMsgBuilder = new LineErrorMsgBuilder(deleteDTO.getFiles().size());
        for (Path file: deleteDTO.getFiles()) {
            try {
                FileUtils.deleteFile(file);
            } catch (IOException e) {
                LOGGER.error("删除文件[{}]失败", file, e);
                lineErrorMsgBuilder.add(String.format("删除文件[%s]失败", file));
            }
        }
        if (lineErrorMsgBuilder.existMsg()) {
            throw BusinessException.createException(ErrorMessage.PARAM_ERROR.getErrorCode(), lineErrorMsgBuilder.buildMsg());
        }
    }

    private static class LineErrorMsgBuilder {
        private final List<String> errorMsgList;

        public LineErrorMsgBuilder(int msgNum) {
            errorMsgList = new ArrayList<>(msgNum);
        }

        public void add(String errorMsg) {
            errorMsgList.add(errorMsg);
        }

        public boolean existMsg() {
            return !errorMsgList.isEmpty();
        }

        public String buildMsg() {
            StringBuilder stringBuilder = new StringBuilder();
            for (String errorMsg: errorMsgList) {
                stringBuilder.append(errorMsg).append("\n");
            }
            return stringBuilder.toString();
        }
    }

    @Component
    private static class FileFilter {
        private final Predicate<Path> filter;
        private final List<Path> ignoreRootFiles;
        @Autowired
        private OnePieceConfig onePieceConfig;

        public FileFilter(@Value("${ignore.file}") List<String> ignoreFileName) {
            LOGGER.info("load ignoreFileName={}", ignoreFileName);
            ignoreRootFiles = generateRootIgnoreFile(ignoreFileName);
            LOGGER.info("ignoreRootFiles={}", ignoreRootFiles);
            filter = instance();
        }

        public Predicate<Path> getFilter() {
            return filter;
        }

        private List<Path> generateRootIgnoreFile(List<String> ignoreFileName) {
            return FileUtils.listRoot().stream()
                    .map(root -> {
                        List<Path> ignoreFile = new ArrayList<>(ignoreFileName.size());
                        for (String ignore : ignoreFileName) {
                            ignoreFile.add(root.resolve(Paths.get(ignore)));
                        }
                        return ignoreFile;
                    }).flatMap(List::stream).collect(Collectors.toList());
        }

        private boolean isIgnoreRootFile(Path path) {
            for (Path ignoreRootFile: ignoreRootFiles) {
                if (ignoreRootFile.equals(path)) return true;
            }
            return false;
        }

        private Predicate<Path> instance() {
            Predicate<Path> filter = path -> !onePieceConfig.onePieceHomePath.equals(path);
            Predicate<Path> filter2 = this::isIgnoreRootFile;
            return filter.and(filter2.negate());
        }
    }
}
